package com.keyleaf.up.document;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.keyleaf.up.document.handler.AbstractDocumentToPdfHandler;
import com.keyleaf.up.document.handler.DefaultToPdfHandler;
import com.keyleaf.up.document.handler.ZipToPdfHandler;
import com.keyleaf.up.document.model.OfficeConfig;
import lombok.Builder;
import lombok.Singular;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdfwriter.compress.CompressParameters;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.tika.Tika;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author keyleaf
 * @since 2025/5/30
 **/
@Slf4j
@Builder
public class DocumentClient {

    private final Tika tika = new Tika();

    private String tmpDir;

    private OfficeConfig officeConfig;

    @Singular
    private List<AbstractDocumentToPdfHandler> documentToPdfHandlers;

    /**
     * 创建一个默认的DocumentClient实例
     *
     * @return DocumentClient实例
     */
    public static DocumentClient defaultClient() {
        OfficeConfig officeConfig = new OfficeConfig();
        return DocumentClient.builder()
                .tmpDir(FileUtil.getTmpDirPath() + "pdfMerge" + FileUtil.FILE_SEPARATOR)
                .officeConfig(officeConfig)
                .documentToPdfHandler(new DefaultToPdfHandler(officeConfig))
                .documentToPdfHandler(new ZipToPdfHandler(officeConfig))
                .build();
    }

    private DocumentClient(String tmpDir, OfficeConfig officeConfig, List<AbstractDocumentToPdfHandler> documentToPdfHandlers) {
        Assert.notNull(officeConfig, "officeConfig can not be null");
        Assert.notEmpty(documentToPdfHandlers, "documentToPdfHandlers can not be empty");
        this.tmpDir = StrUtil.blankToDefault(tmpDir, FileUtil.getTmpDirPath() + "pdfMerge" + FileUtil.FILE_SEPARATOR);
        this.officeConfig = officeConfig;
        this.documentToPdfHandlers = documentToPdfHandlers;

        // 初始化OfficeManager（libreoffice）
        OfficeManager officeManager = new OfficeManager();
        officeManager.start(officeConfig);

        // 注册关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(officeManager::stop));
    }

    public InputStream mergeIntoPdfByUrl(List<String> urls) throws IOException {
        List<File> files = urls.stream().map(url -> {
            String suffix = FileUtil.getSuffix(url);
            String fileName = IdUtil.simpleUUID();
            if (StrUtil.isNotBlank(suffix)) {
                fileName += "." + suffix;
            }
            String target = StrUtil.appendIfMissing(tmpDir, FileUtil.FILE_SEPARATOR) + fileName;
            log.info("正在下载 {} 到 {}", url, target);
            return HttpUtil.downloadFileFromUrl(url, target);
        }).collect(Collectors.toList());
        return mergeIntoPdf(files);
    }


    /**
     * 将多个文件合并成一个pdf
     * <p>
     * 非pdf文件需要先处理成pdf文件
     *
     * @param files 文件列表
     * @return 合并后的pdf文件流
     */
    public InputStream mergeIntoPdf(List<File> files) throws IOException {
        String targetDir = StrUtil.appendIfMissing(tmpDir, FileUtil.FILE_SEPARATOR) + System.currentTimeMillis();
        // 转pdf处理
        List<File> pdfFiles = this.convertToPdf(files, targetDir);
        log.info("即将合并目录 {} 下的pdf文件", targetDir);
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            PDFMergerUtility mergePdf = new PDFMergerUtility();
            for (File file : pdfFiles) {
                mergePdf.addSource(file);
            }
            mergePdf.setDestinationStream(outputStream);
            mergePdf.mergeDocuments(IOUtils.createMemoryOnlyStreamCache(), CompressParameters.NO_COMPRESSION);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());

            File file = FileUtil.writeFromStream(byteArrayInputStream, FileUtil.file(targetDir + FileUtil.FILE_SEPARATOR + "merged.pdf"));
            try (PDDocument pdDocument = Loader.loadPDF(file)) {
                addPageNumbers(pdDocument);
                // 保存修改后的文档
                file = FileUtil.file(targetDir + FileUtil.FILE_SEPARATOR + "merged_with_numbers.pdf");
                pdDocument.save(file);
            }
            return FileUtil.getInputStream(file);
        }
    }

    /**
     * 在每一页的底部添加页码
     */
    private void addPageNumbers(PDDocument document) throws IOException {
        int totalPages = document.getNumberOfPages();
        for (int i = 0; i < totalPages; i++) {
            PDPage page = document.getPage(i);
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                float width = page.getMediaBox().getWidth();

                String pageNumberText = "— " + (i + 1) + " —";
                float fontSize = 10;

                contentStream.beginText();
                contentStream.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), fontSize);
                contentStream.newLineAtOffset(width - 100, fontSize + 20);
                contentStream.showText(pageNumberText);
                contentStream.endText();
            }
        }
    }

    /**
     * 将多个文件转换成pdf文件
     *
     * @param files     文件列表
     * @param targetDir 转换后的pdf文件保存目录
     * @return 转换后的pdf文件列表
     */
    private List<File> convertToPdf(List<File> files, String targetDir) {
        AtomicInteger index = new AtomicInteger(0);
        List<File> pdfFiles = new ArrayList<>();
        files.forEach(file -> {
            this.convertToPdf(file, inputStream -> {
                try {
                    String pdfPath = targetDir + FileUtil.FILE_SEPARATOR + index.getAndIncrement() + "_" + FileUtil.getName(file.getPath()) + ".pdf";
                    FileUtil.writeFromStream(inputStream, FileUtil.file(pdfPath));
                    pdfFiles.add(FileUtil.file(pdfPath));
                } catch (Exception e) {
                    log.error("文件{}转pdf失败:", file.getPath(), e);
                } finally {
                    IoUtil.close(inputStream);
                }
            });
        });
        return pdfFiles;
    }

    /**
     * 文件转pdf
     *
     * @param file     文件
     * @param consumer 消费转换后的pdf文件流
     */
    public void convertToPdf(File file, Consumer<InputStream> consumer) {
        if (file == null) {
            throw new IllegalArgumentException("file can not be null");
        }
        if (file.isDirectory()) {
            throw new IllegalArgumentException("file can not be directory");
        }
        if (consumer == null) {
            throw new IllegalArgumentException("consumer can not be null");
        }
        try {
            if (StrUtil.equalsIgnoreCase("pdf", FileUtil.getSuffix(file))) {
                log.info("文件 {} 已经是pdf文件，无需转换", file.getPath());
                consumer.accept(FileUtil.getInputStream(file));
            } else {
                this.fetchDocumentToPdfHandler(file).handle(new BufferedInputStream(FileUtil.getInputStream(file)), consumer);
            }
            log.info("文件 {} 转pdf成功", file.getPath());
        } catch (Exception e) {
            throw new RuntimeException("文件" + file.getPath() + "转pdf失败:", e);
        }
    }

    /**
     * 根据文件信息获取对应的文件转pdf处理器
     *
     * @param file 文件信息
     * @return 文件转pdf处理器
     */
    private AbstractDocumentToPdfHandler fetchDocumentToPdfHandler(File file) throws IOException {
        String mimeType = tika.detect(file);
        log.info("filePath: {}, mimeType: {}", file.getPath(), mimeType);
        for (AbstractDocumentToPdfHandler documentToPdfHandler : documentToPdfHandlers) {
            if (documentToPdfHandler.support(mimeType)) {
                return documentToPdfHandler;
            }
        }
        throw new IllegalArgumentException("unsupported mimeType:" + mimeType + " " + file.getPath());
    }

}
